
#include <iostream>
#include <sstream>

/* __________________________ SML library ________ */
#include "../inc/segment.h"
#include "../inc/trackball.h"
#include "../inc/segmenttree.h"
#include "../inc/link.h"
#include "../inc/brain.h"

/* ___________________ GLUT & OpenGL Librairies __ */
#ifdef __APPLE__
        #include <OpenGL/gl.h>
        #include <OpenGL/glu.h>
        #include <GLUT/glut.h>
#else
        #ifdef _WIN32
        #include "../glut.h"
        #else
        #include <GL/gl.h>
        #include <GL/glu.h>
        #include <GL/glut.h>
        #endif
#endif

using namespace std;

#define KEYBOARD_NULL 0
#define KEYBOARD_NAME 1
#define KEYBOARD_COLOR 2
#define KEYBOARD_SAVE 3
#define KEYBOARD_LOAD 4
#define KEYBOARD_LOAD_MAP 5
#define KEYBOARD_MAP_THRESH 6
#define KEYBOARD_DEPTH_THRESH 7
#define KEYBOARD_SIZE_THRESH 8

/* ************************************************************************ */
/* ***						  GLOBALS VARIABLES                  		*** */
/* ************************************************************************ */

int		__winX = 500;											/* Window width. */
int		__winY = 500;											/* Window height. */

TrackBall		__trackBall;									/* Trackball. Tool to rotate the objects. */

Mesh			__mesh;											/* Surface of the brain. */
Map				__map;											/* Feature map of the brain. */
SegmentTree*	__segTreeSulcis = new SegmentTree();			/* Tree containing the sulcis of the brain : this root and each of its brothers represent one sulci. */

SegmentTree*	__pickedSulcus = NULL;

Brain			__brain;										/* Brain. */

bool			__brainLoaded = false;
int				__keyboardMode = KEYBOARD_NULL;
string			__thresh;
string			__name;
string			__pathSave;
string			__pathLoadBrain;
string			__pathLoadMap;
string			__colorComponentString;
string			__colorString;
Vector3D		__color;
int				__rgb = 0;

int				__mapThresh = 10;
int				__depthThresh = -20;
double			__sizeThres = 0.3;
Map				__mapCpy;

int				__displayMeshMode = FACES;

/* ************************************************************************ */
/* ***						FUNCTIONS PROTOTYPES                  		*** */
/* ************************************************************************ */

string	float2string		(float f);								/* Returns a string containing the float. */
string	getExtension		( string _s );							/* Returns the extension of a given file name. */
void	keyboardManagement	( unsigned char _k, int& _mode );		/* Manages the keyboard mode events. */
void	displayString		( string _s, int _x, int _y, float _r = 1, float _g = 1, float _b = 0 );		/* OpenGL routine to display a string at the screen. */
void	displayContext		( int _mode );							/* Display some informations for the user. */
void	displayGL			();										/* GLUT callback for rendering. */
void	reshapeGL			( int _w, int _h );						/* GLUT callback for resizing the window. */
void	initGL				();										/* GLUT OpenGL rendering initialization. */
void	keyboardGL			( unsigned char _k, int _x, int _y );	/* GLUT callback for keyboard typing. */
int		main				( int _argc, char** _argv );			/* Main function, launches the glut loop. */

/* ************************************************************************ */
/* ***						  MAIN PROGRAM								*** */
/* ************************************************************************ */

string float2string(float f)
{
	ostringstream os;
	os << f;
	return os.str();
}

string getExtension(string _s)
{
	size_t posPoint;
	posPoint = _s.find_last_of(".");
	return _s.substr( posPoint );
}

void keyboardManagement(unsigned char _k, int& _mode)
{
	switch ( _mode )
	{
		case KEYBOARD_NULL :
			if ( (_k == 'n' || _k == 'N') && __pickedSulcus != NULL )
			{
				__name.clear();
				_mode = KEYBOARD_NAME;
			}
			
			if ( (_k == 'c' || _k == 'C') && __pickedSulcus != NULL )
			{
				__rgb = 0;
				__colorComponentString.clear();
				_mode = KEYBOARD_COLOR;
			}
			
			if ( _k == 's' || _k == 'S' )
			{
				__pathSave.clear();
				_mode = KEYBOARD_SAVE;
			}
			
			if ( _k == 'l' || _k == 'S' )
			{
				__pathLoadBrain.clear();
				__keyboardMode = KEYBOARD_LOAD;
			}
			
			if ( _k == 't' || _k == 'T' )
			{
				__thresh.clear();
				__keyboardMode = KEYBOARD_MAP_THRESH;
			}
			
			if ( (_k == 'i' || _k == 'I') && __pickedSulcus != NULL )
				__pickedSulcus->print();
			
			break;
			
		case KEYBOARD_NAME :
			if ( _k == 13 /* Enter touch. */ )
			{
				__pickedSulcus->setName( __name );
				_mode = KEYBOARD_NULL;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __name.size() > 0 )
						__name.erase( __name.end()-1 );
				}
				
				else
					__name.push_back( _k );
			}
			break;
			
		case KEYBOARD_COLOR :
			if ( _k == 13 /* Enter touch. */ )
			{
				__colorString.clear();
				__color.setZ( atof ( __colorComponentString.c_str() ) );
				__pickedSulcus->setColor( __color );
				_mode = KEYBOARD_NULL;
			}
			
			else
			{
				__colorString.push_back( _k );
				
				if ( _k == ' ' )
				{
					if ( __rgb == 0 )
					{
						__color.setX( atof ( __colorComponentString.c_str() ) );
						__colorComponentString.clear();
					}
					
					if ( __rgb == 1 )
					{
						__color.setY( atof ( __colorComponentString.c_str() ) );
						__colorComponentString.clear();
					}
					
					__rgb++;
				}
				
				else
					__colorComponentString.push_back( _k );
			}
			break;
			
		case KEYBOARD_SAVE :
			if ( _k == 13 /* Enter touch. */ )
			{
				/* I did not write any const method so... */
				__brain.save( const_cast<char *> (__pathSave.c_str()) );
				__keyboardMode = KEYBOARD_NULL;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __pathSave.size() > 0 )
						__pathSave.erase( __pathSave.end()-1 );
				}
				
				else
					__pathSave.push_back( _k );
			}
			
			break;
			
		case KEYBOARD_LOAD :
			if ( _k == 13 /* Enter touch. */ )
			{
				if ( getExtension( __pathLoadBrain) == ".brn" )
				{
					string newWinName;
					size_t pos = __pathLoadBrain.find_last_of( "/" );
					if ( pos != string::npos )
						newWinName = __pathLoadBrain.substr( pos +1 );
					else
						newWinName = __pathLoadBrain;
					
					glutSetWindowTitle( newWinName.c_str() );
					
					__brain.clear();
					/* I did not write any const method so... */
					__brain.load( const_cast<char *> (__pathLoadBrain.c_str()) );
					__brainLoaded = true;
					
					__keyboardMode = KEYBOARD_NULL;
				}
				
				if ( getExtension( __pathLoadBrain) == ".obj" )
				{
					__brain.clear();
					
					string newWinName;
					size_t pos = __pathLoadBrain.find_last_of( "/" );
					if ( pos != string::npos )
						newWinName = __pathLoadBrain.substr( pos +1 );
					else
						newWinName = __pathLoadBrain;
					
					glutSetWindowTitle( newWinName.c_str() );
					
					__mesh.loadOBJ( const_cast<char *> (__pathLoadBrain.c_str()) );
					__mesh.computeNormals();
					__mesh.normalize();
					
					__pathLoadMap.clear();
					__keyboardMode = KEYBOARD_LOAD_MAP;
				}
				
				else
				{
					cout<<"Non allowed extension file format"<<endl;
					__keyboardMode = KEYBOARD_NULL;
				}
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __pathLoadBrain.size() > 0 )
						__pathLoadBrain.erase( __pathLoadBrain.end()-1 );
				}
				
				else
					__pathLoadBrain.push_back( _k );
			}
			
			break;
			
		case KEYBOARD_LOAD_MAP :
			if ( _k == 13 /* Enter touch. */ )
			{
				/* I did not write any const method so... */
				__map.load( const_cast<char *> (__pathLoadMap.c_str()) );
				__mapCpy = __map;
				
				/* Loading and coupling map to the mesh. */
				__map.segment( __mapThresh );
				__mesh.colorFromMap( __map );
				__segTreeSulcis->createFromMap( LESSER_EQUAL, __depthThresh, __mesh, __map );
//				__segTreeSulcis= SegmentTree::filterSize( __segTreeSulcis, __sizeThres );
				__segTreeSulcis= SegmentTree::filterArea( __brain.getMesh(), __segTreeSulcis, __sizeThres );
				
				/* Creating the brain. */
				__brain.setMesh( &__mesh );
				__brain.setMap( &__map );
				__brain.setSulcis( __segTreeSulcis );
				__brain.setGyris( NULL );
				__brain.colorElements();
				__brainLoaded = true;
				
				
				__keyboardMode = KEYBOARD_NULL;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __pathLoadMap.size() > 0 )
						__pathLoadMap.erase( __pathLoadMap.end()-1 );
				}
				
				else
					__pathLoadMap.push_back( _k );
			}
			
			break;
			
		case KEYBOARD_MAP_THRESH :
			if ( _k == 13 /* Enter touch. */ )
			{
				__mapThresh = atoi ( __thresh.c_str() );
				__thresh.clear();
				__keyboardMode = KEYBOARD_DEPTH_THRESH;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __thresh.size() > 0 )
						__thresh.erase( __thresh.end()-1 );
				}
				
				else
					__thresh.push_back( _k );
			}
			break;
			
		case KEYBOARD_DEPTH_THRESH :
			if ( _k == 13 /* Enter touch. */ )
			{
				__depthThresh = atoi ( __thresh.c_str() );
				__thresh.clear();
				__keyboardMode = KEYBOARD_SIZE_THRESH;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __thresh.size() > 0 )
						__thresh.erase( __thresh.end()-1 );
				}
				
				else
					__thresh.push_back( _k );
			}
			break;
			
		case KEYBOARD_SIZE_THRESH :
			if ( _k == 13 /* Enter touch. */ )
			{
				__sizeThres = atoi ( __thresh.c_str() );				
				__keyboardMode = KEYBOARD_NULL;
			}
			
			else
			{
				if ( _k == 8 /* Backspace. */ )
				{
					if ( __thresh.size() > 0 )
						__thresh.erase( __thresh.end()-1 );
				}
				
				else
					__thresh.push_back( _k );
			}
			break;
			
		default :
			break;
	}
}

void displayString(string _s, int _x, int _y, float _r, float _g, float _b)
{
	/* ___________________________________________________________ Set the display mode to a 2D drawing. _____ */
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0,__winX,0,__winY);
	glMatrixMode(GL_MODELVIEW);
	glDisable( GL_LIGHTING );
	glPushMatrix();
	glLoadIdentity();
	/* _______________________________________________________________________________________________________ */
	
	/* We then display the string in color. */
	glColor3f( _r, _g, _b );
	for ( int j = 0 ; j < (int)_s.size() ; j++ )
	{
		glRasterPos2i(  _x + (j*8) , _y );
		glutBitmapCharacter(GLUT_BITMAP_8_BY_13, (int)_s[j] );
	}	
	
	/* ___________________________________________________ Reset the display mode to a 3D scene drawing. _____ */
	glPopMatrix();
	glEnable( GL_LIGHTING );
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	/* _______________________________________________________________________________________________________ */
}

void displayContext(int _mode)
{
	switch ( _mode )
	{
	case KEYBOARD_NAME :
		displayString( "Type in a name for the selected element :", 10, 40 );
		displayString( __name, 10, 20 );
		break;
		
	case KEYBOARD_COLOR :
		displayString("Please select a color giving three floats between 0 and 1 :", 10, 40 );
		displayString( __colorString, 10, 20 );
		break;
		
	case KEYBOARD_SAVE :
		displayString("Save as :", 10, 40 );
		displayString( __pathSave, 10, 20 );
		break;
		
	case KEYBOARD_LOAD :
		displayString("Load file (.br format) :", 10, 40 );
		displayString( __pathLoadBrain, 10, 20 );
		break;
		
	case KEYBOARD_LOAD_MAP :
		displayString("Load map of the mesh file :", 10, 40 );
		displayString( __pathLoadMap, 10, 20 );
		break;
		
	case KEYBOARD_MAP_THRESH :
		displayString("Number of segment you want the map to be divided into :", 10, 40 );
		displayString( __thresh, 10, 20 );
		break;
		
	case KEYBOARD_DEPTH_THRESH :
		displayString("Maximum depth a segment will be considered as a sulcus [-50,0] :", 10, 40 );
		displayString( __thresh, 10, 20 );
		break;
		
	case KEYBOARD_SIZE_THRESH :
		displayString("Minimum size for a segment to be considered as a sulcus : ", 10, 40 );
		displayString( __thresh, 10, 20 );
		break;
		
	case KEYBOARD_NULL :
		if ( __pickedSulcus == NULL )
		{
			displayString( "Select a sulcus clicking the right button", 10, 80 );
			displayString( "s -> Save the brain. Expected format : .brn", 10, 60);
			displayString( "l -> Load a brain. Expected formats : .brn OR .obj", 10, 40);
			displayString( "t -> Select other thresholds", 10, 20);
		}
		
		else
		{
			displayString("n -> Enter the name selection mode", 10, 60);
			displayString("c -> enter the color selection mode", 10, 40 );
			displayString("i -> get some informations about the selected sulcus", 10, 20 );
		}
		
		break;
		
	default :
			break;
	}
}

void displayGL ()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	__trackBall.displayBeginGL_GLUT();
	
		__mesh.display( __displayMeshMode );
		
	__trackBall.displayEndGL_GLUT();
	
	displayContext( __keyboardMode );
	
	glutSwapBuffers();
}

void reshapeGL(int _w, int _h)
{
       __winX = _w;
       __winY = _h;

		glViewport(0, 0,__winX,__winY);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		
		gluPerspective(60.0, (GLfloat)_w/(GLfloat)_h, 0.1, 500.0);
		
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		gluLookAt( -1.5, 0, 0,
				   0, 0, 0,
				   0, 0, 1 );
		
        glutPostRedisplay();
}

void keyboardGL(unsigned char _k, int _x, int _y)
{
	if ( _k == 27 || _k == 'q' || _k == 'Q' )
		exit(0);
	
	if ( _k == 'v' || _k == 'V' )
		__displayMeshMode = VERTICES;
	
	if ( _k == 'e' || _k == 'E' )
		__displayMeshMode = EDGES;
	
	if ( _k == 'f' || _k == 'f' )
		__displayMeshMode = SMOOTH;
	
	keyboardManagement( _k, __keyboardMode );
	
	glutPostRedisplay();
}

void specialKeyboardGL(int _k, int _x, int _y)
{
	if ( _k == GLUT_KEY_UP )
	{
		;
	}
	
	if ( _k == GLUT_KEY_DOWN )
	{
		;
	}
	
	if ( _k == GLUT_KEY_RIGHT )
	{
		;
	}
	
	if ( _k == GLUT_KEY_LEFT )
	{
		;
	}
	
	glutPostRedisplay();
}

void mouseGL (int _button, int _state, int _x, int _y)
{
	__trackBall.mouseGL_GLUT( GLUT_LEFT_BUTTON, _button, _state, _x, _y );
	
	if ( _button == GLUT_RIGHT_BUTTON && _state == GLUT_DOWN )
		__pickedSulcus = __brain.pickingNode( _x, _y, SULCIS, &__trackBall );
	
	glutPostRedisplay();
}

void motionGL (int _x, int _y)
{
	__trackBall.motionGL_GLUT( _x, _y );
	
	glutPostRedisplay();
}

void initGL()
{
        GLfloat mat_specular   [4] = { 0.18, 0.18, 0.18, 0.18 };
        GLfloat mat_shininess  [ ] = { 128 };
        GLfloat global_ambient [ ] = { 0.02, 0.02, 0.05, 0.05 };
        GLfloat light0_ambient [ ] = { 0, 0, 0, 0 };
        GLfloat light0_diffuse [ ] = { 0.85, 0.85, 0.8, 0.85 };
        GLfloat light0_specular[ ] = { 0.85, 0.85, 0.15, 0.85 };

        glClearColor(0,0,0,0);

		glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
		
		glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);
		
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_NORMALIZE);
		
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);       
}

int main ( int _argc, char** _argv )
{
	string brainName;
	
	if ( _argc > 1 )
	{
		brainName = _argv[1];		
		size_t posName = brainName.find_last_of( "/" );
		if ( posName != string::npos )
			brainName = brainName.substr( posName );
	}
	
	if ( _argc == 2 )
	{
		if ( strcmp ( _argv[1], "-h" ) == 0 || strcmp ( _argv[1], "-help" ) == 0 )
		{
			cout<<"|"<<endl;
			cout<<"| SulEd : Sulcis Editor."<<endl;
			cout<<"| Piece of software allowing a user to label a brain's sulcis"<<endl;
			cout<<"|"<<endl;
			cout<<endl;
			cout<<"How to :"<<endl;
			cout<<"-thresh"<<endl;
			cout<<"\tmapThresh : Number of value ranges you want to segment the map (>0)"<<endl;
			cout<<"\tdepthThresh : Depth below which a segment is considred as a sulcus ([-50,0])"<<endl;
			cout<<"\tsizeThresh : Size below which a segment is considered as noise on the brain surface (>0)"<<endl;
			cout<<"Usage : "<<_argv[0]<<" [mesh.obj map.txt [-thresh mapThresh depthThresh sizeThresh] ]"<<endl;
			return 0;
		}
	}
	
	/* Loading the brain informations. */
	__mesh.loadOBJ( _argv[1] );													/* Loading the source mesh. */
	__mesh.computeNormals();													/* Computing its normals. */
	__mesh.normalize();															/* Resize it to a unit length box. */
	__map.load( _argv[2] );														/* Load the source feature map. */
	__mapCpy.load( _argv[3] );
	__map.increaseContrast( CUBE, 0.5, 5 );
	__map.segment( __mapThresh );																/* Segmentate the map in 10 segments (can be given in parameter). */
	__mesh.colorFromMap( __map );																/* Setting colors for each vertex of the mesh according to it's map value. */
		
	/* ************************************************************************ */
	/* ***					CREATING WINDOW WITH GLUT						*** */
	/* ************************************************************************ */
	glutInit(&_argc, _argv);
	
	glutInitWindowSize( glutGet( GLUT_SCREEN_WIDTH ) , glutGet( GLUT_SCREEN_HEIGHT ) );
	glutInitWindowPosition(0,0);

	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);	
	glutCreateWindow( brainName.c_str() );
	glutDisplayFunc(displayGL);
	glutReshapeFunc(reshapeGL);
	glutKeyboardFunc(keyboardGL);
	glutSpecialFunc(specialKeyboardGL);
	glutMouseFunc(mouseGL);
	glutMotionFunc(motionGL);
	initGL();
	
	glutMainLoop();
	
	return 0;
}
